// Copyright (C) 2000 BotS Q3 Team
//

/*****************************************************************************
 * name:		bots_main.c
 *
 * desc:		Quake3 Battle of the Sexes General Routines
 *
 * $Archive: /source/code/game/bots_main.c $
 * $Author: tbp $
 * $Revision: 1.7 $
 * $Modtime: 1/15/00 2:04a $
 * $Date: 2001/01/10 22:37:34 $
 *
 *****************************************************************************/


#include "g_local.h"
#include "bots_local.h"
#include "bots_captain.h"
#include "bots_scientist.h"
#include "bots_bodyguard.h"
#include "bots_sniper.h"
#include "bots_soldier.h"
#include "bots_berzerker.h"
#include "bots_infiltrator.h"
#include "bots_kamikazee.h"
#include "bots_nurse.h"


#include "bots_game2cgame.h"



gentity_t *bots_getCaptain (int ai_team) {
	return bots_teams[ai_team-1].cachedCaptain;
}

gentity_t *bots_getScientist (int ai_team) {
	return bots_teams[ai_team-1].cachedScientist;
}

int bots_getGlobalCount() {
	return bots_teams[0].members + bots_teams[1].members;
}

// Scan all players and update some pointers.
// Done on every frame, before any client update sequence
void bots_cacheCaptainAndSci() {
	int			i;
	gentity_t	*ent = g_entities;


	// Reset
	bots_teams[0].cachedCaptain = bots_teams[0].cachedScientist = NULL;
	bots_teams[1].cachedCaptain = bots_teams[1].cachedScientist = NULL;
	
	bots_teams[0].members = 0;
	bots_teams[1].members = 0;

	// Scan
	for (i = 0; i < level.maxclients; i++, ent++) {
		if (ent->inuse && ent->client) {
			int team = ent->bots_team;
			
			if ((team == TEAM_RED) || (team == TEAM_BLUE)) {
				team--;
				
				switch (ent->bots_class) {
				case BOTS_CAPTAIN:
					bots_teams[team].cachedCaptain = ent;
					break;
				case BOTS_SCIENTIST:
					bots_teams[team].cachedScientist = ent;
					break;
				}

				bots_teams[team].members++; // Count members at the same time
			}
			
		}
	}
}


static void bots_update_upgrades(bots_sci_upgrades_t *p, int iTeam) {
	int			i,j;
	gentity_t	*e = &g_entities[0];


	// Reset counts
	for (i = 0; i < BOTS_MAX_CLASSES; i++)
		p->classes[i].count = 0;

	// Scan
	for (i = 0; i < level.maxclients; i++) 	{
		if ((e->client && e->bots_team == iTeam && e->client->sess.sessionTeam != TEAM_SPECTATOR) &&
			(e->bots_class > 0 && e->bots_class <= BOTS_MAX_CLASSES))

			// only count players with at least one point :P
			if (e->client->ps.persistant[PERS_SCORE] > 0)
				p->classes[e->bots_class-1].count++;
				
		e++;
	}

	// Pre compute some values
	for (i = 0; i < BOTS_MAX_CLASSES; i++) {
		bots_sci_class_t	*c = &p->classes[i];
		float				fCount = c->count;


		for (j=0; j < MAX_SCI_UPGRADES; j++) {
			if (fCount == 0.0f)
				c->values[j] = 0.0f;
			else 
				c->values[j] = (float)c->levels[j]/fCount;
		}
	}

	// Stamp!
	p->timestamp = level.framenum;
}

// Mastah Caller:
// It's done on every frame, but only *once* :p
// Note: i shoulda merged the captain/sci & tech stuff, but i'm too lazy
void bots_cacheMiscTeamStuff() {
	bots_cacheCaptainAndSci();

	// Update the tech stuff if there's a sci
	if (bots_teams[0].cachedScientist) {
		bots_update_upgrades(&bots_teams[0].sci_upgrades, 1);
	}

	if (bots_teams[1].cachedScientist) {
		bots_update_upgrades(&bots_teams[1].sci_upgrades, 2);
	}


	// Fairness? ;)
	if (!bots_fairTeams(-1)) {
		static int spamstamp = 0;

		if (level.time > spamstamp) {
			BOTS_cp_console(-1, "Teams are not fair.\nUntil they're fixed, scores are frozen.");
			spamstamp = level.time + 10000;
		}
	}
}



// I've moved that function here, so it has better chances to get inlined by compilers.
float bots_getTechLevel(gentity_t *ent, int iUpgrade) {
	int					
		iClass= ent->bots_class - 1,
		iTeam = ent->bots_team - 1;
	bots_sci_upgrades_t *p = &bots_teams[iTeam].sci_upgrades;


	// No score or no sci => no upgrade
	if ((ent->client->ps.persistant[PERS_SCORE] < 1) || (!bots_teams[iTeam].cachedScientist))
		return 0.0f;
		 
	iUpgrade--;
	return p->classes[iClass].values[iUpgrade];
}


// An example function to copy and paste into your new function to avoid typing the
// switch statement all of the time

/*
qboolean bots_Example (gentity_t *ent) {
	int		li_class;
	int		li_team;
	int		li_modifier;

	if (!ent->client)
		return qfalse;

	li_team		= ent->bots_team;
	li_class	= ent->bots_class;
	li_modifier = ent->client->pers.classlevel;

	switch (li_class)
	{
		case BOTS_CAPTAIN:
			break;
		case BOTS_BODYGUARD:
			break;
		case BOTS_SNIPER:
			break;
		case BOTS_SOLDIER:
			break;
		case BOTS_BERZERKER:
			break;
		case BOTS_INFILTRATOR:
			break;
		case BOTS_KAMIKAZEE:
			break;
		case BOTS_NURSE:
			break;
		case BOTS_SCIENTIST:
			break;
	}

	return qtrue;
}
*/

void bots_ClientConnect( gentity_t *ent ) {
// BEGIN bots - tbp
	// Client is either:
	// . (re)connecting, clear the crap and get a fresh start
	// . the level was restarted (map_restart), try to preserve his team
	ent->bots_class = 0;
	ent->bots_team = 0;

	// Session
	ent->client->sess.sessionTeam		= TEAM_SPECTATOR;
	ent->client->sess.spectatorState	= SPECTATOR_FREE;
	ent->client->sess.spectatorTime		= level.time;
// END bots - tbp
}


qboolean bots_InitClass( gentity_t *ent ) {

	int			li_class;
	int			li_team;
	int			li_modifier;
	int			li_primaryWeapon;
	gclient_t	*client;
	int			clientNum;


	// Standard check to make sure the client is valid
	if (!ent->client)
		return qfalse;

	client = ent->client;
	clientNum = client - level.clients;

	li_modifier = ent->client->pers.classlevel;
	li_class	= ent->bots_class;
	li_team		= ent->bots_team;

	li_primaryWeapon = WP_GAUNTLET;

	switch (li_class)
	{
		case BOTS_CAPTAIN:
			ent->client->ps.stats[STAT_WEAPONS] = ( 1 << WP_BFG );
			li_primaryWeapon = WP_BFG;
			BOTS_compute_captain_level(li_team);
			break;
		case BOTS_BODYGUARD:
			ent->client->ps.stats[STAT_WEAPONS] = ( 1 << WP_SHOTGUN );
			ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GRAPPLING_HOOK );
			ent->client->ps.ammo[WP_GRAPPLING_HOOK] = -1;
			li_primaryWeapon = WP_SHOTGUN;
			break;
		case BOTS_SNIPER:
			ent->client->ps.stats[STAT_WEAPONS] = ( 1 << WP_RAILGUN );
			ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GRAPPLING_HOOK );
			ent->client->ps.ammo[WP_GRAPPLING_HOOK] = -1;
			li_primaryWeapon = WP_RAILGUN;
			break;
		case BOTS_SOLDIER:
			ent->client->ps.stats[STAT_WEAPONS] = ( 1 << WP_ROCKET_LAUNCHER );
			li_primaryWeapon = WP_ROCKET_LAUNCHER;
			break;
		case BOTS_BERZERKER:
			ent->client->ps.stats[STAT_WEAPONS] = ( 1 << WP_GAUNTLET );
			ent->client->ps.ammo[WP_GAUNTLET] = -1;
			li_primaryWeapon = WP_GAUNTLET;
			break;
		case BOTS_INFILTRATOR:
			ent->client->ps.stats[STAT_WEAPONS] = ( 1 << WP_PLASMAGUN );
			ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GRAPPLING_HOOK );
			ent->client->ps.ammo[WP_GRAPPLING_HOOK] = -1;
			li_primaryWeapon = WP_PLASMAGUN;
			break;
		case BOTS_KAMIKAZEE:
			ent->client->ps.stats[STAT_WEAPONS] = ( 1 << WP_GRENADE_LAUNCHER );
			ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GRAPPLING_HOOK );
			ent->client->ps.ammo[WP_GRAPPLING_HOOK] = -1;
			li_primaryWeapon = WP_GRENADE_LAUNCHER;
			break;
		case BOTS_NURSE:
			ent->client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN );
			ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GRAPPLING_HOOK );
			ent->client->ps.ammo[WP_GRAPPLING_HOOK] = -1;
			li_primaryWeapon = WP_MACHINEGUN;
			break;
		case BOTS_SCIENTIST:
			ent->client->ps.stats[STAT_WEAPONS] = ( 1 << WP_LIGHTNING );
			ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GRAPPLING_HOOK );
			ent->client->ps.ammo[WP_GRAPPLING_HOOK] = -1;
			li_primaryWeapon = WP_LIGHTNING;
			break;
	}

	if (li_primaryWeapon != WP_GAUNTLET) {
		ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_STINGER );
		ent->client->ps.ammo[WP_STINGER] = -1;

//		ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_KNIFE );
//		ent->client->ps.ammo[WP_KNIFE] = -1;

		if (li_modifier > 0)
			ent->client->ps.ammo[li_primaryWeapon] = ent->client->pers.maxammo[li_primaryWeapon];
		else
			ent->client->ps.ammo[li_primaryWeapon] = 10;
	}

	if (li_modifier > 0)
		ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_ARMOR];

	if (li_primaryWeapon != WP_BFG) {
		ent->client->ps.ammo[WP_BFG] = 50;
	}

	ent->client->ps.weapon = li_primaryWeapon;
	ent->client->ps.weaponstate = WEAPON_READY;

	bots_updateClientData(ent);

	return qtrue;
}

void bots_InitClient(gentity_t* ent)
{
	// all classes
	ent->bg_protector			= NULL;
	ent->client->keyindex		= 0;
	ent->client->weapontoggle	= qfalse;

	ent->client->i_grenTossTime = 0;
	ent->client->i_numProx		= 0;
	ent->client->i_powerup		= PW_NONE;
	ent->client->qb_blink		= qfalse;

	ent->client->trooptransport = NULL;
	ent->client->iTroopRank		= 0;

	switch (ent->bots_class)
	{
	case BOTS_CAPTAIN:
		ent->client->qb_proxMode	= qtrue;
		ent->client->i_splitmode	= 1;
		break;

	case BOTS_BODYGUARD:
		ent->laserbeam					= NULL;
		ent->laserbeam2					= NULL;
		ent->client->protect			= qfalse;
		ent->team_protect				= NULL;
		ent->client->i_laserStartTime	= 0;
		ent->client->i_laserStopTime	= 0;
		ent->client->i_laserMaxTime		= 0;
		ent->client->i_laserPulseTime	= 0;
		ent->client->i_laserCoolDown	= 0;
		ent->client->i_decoyStopTime	= 0;
		ent->client->qb_decoyMode		= qfalse;
		ent->client->qb_laserPulseMode	= qfalse;
		ent->client->qb_laserState		= qfalse;
		ent->client->qb_proxMode		= qtrue;

		ent->client->iLaserSetupFreezeTime = 0;
		break;

	case BOTS_SNIPER:
		ent->lasersight				= NULL;
		//ent->alarm1				= NULL;
		//ent->alarm2				= NULL;
		//ent->alarm3				= NULL;
		//ent->alarm1name[0]		= 0;
		//ent->alarm2name[0]		= 0;
		//ent->alarm3name[0]		= 0;
		//ent->flagalarm1name[0]	= 0;
		//ent->flagalarm2name[0]	= 0;
		//ent->flagalarm3name[0]	= 0;
		//ent->alarmmsg[0]			= 0;
		ent->client->qb_cloakWalk	= qfalse;
		ent->client->i_sightTime	= 0;
		ent->client->invisible		= qfalse;
		ent->client->ps.powerups[PW_INVIS] = 0;
		ent->client->last_movement	= level.time;
		VectorCopy(ent->s.pos.trBase, ent->client->last_position);
		break;

	case BOTS_SOLDIER:
		ent->client->qb_guide		= qfalse;
		ent->client->qb_conquerMode	= qfalse;
		ent->client->i_splitmode	= 1;
		ent->client->qb_tagMode		= qfalse;
		ent->client->i_tagNum		= 0;
		ent->client->i_tagTime		= level.time;
		ent->tagEnt					= NULL;
		ent->tagEntbck				= NULL;
		break;

	case BOTS_BERZERKER:
		if (ent->bots_team == TEAM_RED)
			ent->client->i_quadStartTime = level.time + ZERK_QUAD_RED;
		else
			ent->client->i_quadStartTime = level.time + ZERK_QUAD_BLUE;
		ent->client->i_nextChargeTime	= 0;
		ent->client->i_rageStopTime		= 0;
		ent->client->qb_rageMode		= qfalse;
		ent->client->qb_chargeMode		= qfalse;
		ent->client->qb_togquad 		= qtrue;
		ent->client->iOnboard			= 0;
		break;

	case BOTS_INFILTRATOR:
		ent->client->qb_stealMode		= qfalse;
		ent->client->i_disguiseStopTime = level.time;
		ent->client->qb_decoyGrenMode	= qtrue;
		ent->client->i_numDecoys		= 0;
		break;

	case BOTS_KAMIKAZEE:
		ent->client->i_dettrap1			= 0;
		ent->client->i_dettrap2			= 0;
		ent->client->qb_settrap1		= qfalse;
		ent->client->qb_settrap2		= qfalse;
		ent->client->qb_detMode			= qfalse;
		ent->client->qb_normalMode		= qtrue;
		ent->client->qb_clusterMode		= qfalse;
		ent->client->qb_autorewireMode	= qfalse;
		ent->client->qb_longrangeMode	= qfalse;
		ent->client->qb_stickMode		= qfalse;
		ent->client->i_rewireTime		= 0;
		break;

	case BOTS_NURSE:
		ent->client->qb_poisonMode		= qfalse;
		ent->client->i_lastHealTime		= 0;
		ent->client->qb_flashMode		= qtrue;
		break;

	case BOTS_SCIENTIST:
		ent->client->qb_teleportMode	= qtrue;
		break;
	}

	bots_updateClientData(ent);
}

void BOTS_killCam(int clientNum);

void bots_PlayerDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
{
	gentity_t	*enemy;

	if (!self->client)
		return;

	if (self->bots_class == BOTS_CAPTAIN && attacker->client) {
		if (self == attacker && !self->usedkillcommand) {
			if (self->bots_team == TEAM_RED)
				enemy = bots_getCaptain(TEAM_BLUE);
			else
				enemy = bots_getCaptain(TEAM_RED);

			if (enemy)
				bots_AddScore_Promo(enemy, 0, BOTS_SCORE_CAPKILL, BOTS_PROMOPTS_CAPKILL);
		}
		else if (self != attacker && self->bots_team != attacker->bots_team)
			bots_AddScore_Promo(attacker, BOTS_FRAG_CAPTAIN_BONUS, BOTS_SCORE_CAPKILL, BOTS_PROMOPTS_CAPKILL);
	}
	else if (self->bots_class == BOTS_SCIENTIST && attacker->client) {
		if (self == attacker && !self->usedkillcommand) {
			if (self->bots_team == TEAM_RED)
				enemy = bots_getScientist(TEAM_BLUE);
			else
				enemy = bots_getScientist(TEAM_RED);
			if (enemy)
				bots_AddScore_Tech(enemy, BOTS_TECHPTS_SCIKILL);
		}
		else if (self != attacker && self->bots_team != attacker->bots_team)
			bots_AddScore_Tech(attacker, BOTS_TECHPTS_SCIKILL);
	}
	else if (self->bots_team != attacker->bots_team && attacker->bots_class == BOTS_SCIENTIST) {
		// Give the Scientist a tech point for going out there and getting a frag!
		bots_AddScore_Tech(attacker, BOTS_TECHPTS_SCIKILL);
	}

	// striping code
	if (self != attacker						&&
		attacker->client						&&
		self->bots_team != attacker->bots_team	&&
		self->bots_class != BOTS_SCIENTIST		&&
		attacker->bots_class != BOTS_CAPTAIN	&&
		attacker->bots_class != BOTS_SCIENTIST	&&
		meansOfDeath != MOD_TARGET_LASER		&&
		meansOfDeath != MOD_NUKE				&&
		meansOfDeath != MOD_POISON				&&
		meansOfDeath != MOD_TENDRIL				&&
		(!self->client->protect && !self->team_protect))
	{
		if (self->client->pers.classlevel > (attacker->client->pers.classlevel + 1) && self->client->pers.classlevel > 1) {
			if (self->bots_class == BOTS_CAPTAIN)
			{
				bots_AddScore(self, 0, BOTS_SCORE_PENALTY,self->r.currentOrigin );
				BOTS_cp_console(self->s.clientNum, va("Your team has lost a point!\nYou were killed by someone at level %i!", attacker->client->pers.classlevel));
			}
			else
			{
				self->client->pers.classlevel--;
				BOTS_cp_console(self->s.clientNum, va("You have been striped!\nYou are now level %i!", self->client->pers.classlevel));

				attacker->client->pers.classlevel++;
				BOTS_cp_console(attacker->s.clientNum, va("You earned a stripe!\nYou are now level %i!", attacker->client->pers.classlevel));
			}
		}
	}

	// Just kept here in case we use it for something in the future
	if (self->usedkillcommand)
		self->usedkillcommand = qfalse;

	self->bg_protector = NULL;

	if (self->client->trooptransport)
		BOTS_BerzerkerReleaseOne(self);

	if (self->flashlight)
		BOTS_flashlight(self->s.clientNum);

	// destroy all proxies belonging to the player
	//
	// this needs to happen before the class specific clean up
	//
	if (self->bots_class == BOTS_CAPTAIN   ||
		self->bots_class == BOTS_BODYGUARD ||
		self->bots_class == BOTS_INFILTRATOR)
	{
		int i;
		for (i = 0; i < level.num_entities; i++)
		{
			gentity_t*	grenade = g_entities + i;

			if (!grenade)
				continue;
			if (!grenade->classname)
				continue;
			if (Q_stricmp (grenade->classname, "proxy_grenade") != 0 &&
				Q_stricmp (grenade->classname, "decoy_grenade") != 0)
				continue;
			if (grenade->parent != self)
				continue;

			grenade->think = G_ExplodeMissile;
			grenade->nextthink = level.time + 400;
		}
	}

	// Class specific clean up
	switch (self->bots_class)
	{
	case BOTS_CAPTAIN:
		BOTS_killCam(self->s.clientNum);
/*
		if (self->client->pers.autoimpeach) {
			char	   netname[30];

			self->client->pers.autoimpeach = qfalse;
			self->client->pers.forcekill = 0;

			strcpy(netname, self->client->pers.netname);
			Q_CleanStr(netname);
			BOTS_cp_console(-1, va("%s has been impeached.", netname));
			self->client->pers.impeached = qtrue;
			bots_ChangeClass (self, BOTS_BODYGUARD);
		}
*/
		break;

	case BOTS_BERZERKER:
		self->client->i_quadStartTime	= level.time;
		self->client->i_nextChargeTime	= level.time;
		self->client->i_rageStopTime	= level.time;
		self->client->qb_rageMode		= qfalse;
		self->client->qb_chargeMode		= qfalse;
		self->client->qb_togquad		= qtrue;
		BOTS_BerzerkerReleaseTroops(self);
		break;

	case BOTS_BODYGUARD:
		/*
		if (self->client->protect)
		{
			self->client->protect = qfalse;

			// clear protector and protected fields
			if (self->team_protect)
			{
				self->team_protect->bg_protector = NULL;
				self->team_protect = NULL;

				//gentity_t* player = g_entities + ent->team_protect->s.clientNum;
			}
		}// protect mode
		*/
		BOTS_BG_CleanProtect(self);

		// zero decoy counter
		if (self->client->qb_decoyMode)
		{
			self->client->ps.powerups[PW_REDFLAG] = 0;
			self->client->ps.powerups[PW_BLUEFLAG] = 0;
		}

		BOTS_laserkill(self->s.clientNum,qtrue);

		self->laserbeam			= NULL;
		self->laserbeam2		= NULL;
		self->client->protect	= qfalse;
		self->team_protect		= NULL;
		self->client->i_laserStartTime	= 0;
		self->client->i_laserStopTime	= 0;
		self->client->i_laserMaxTime	= 0;
		self->client->i_laserPulseTime	= 0;
		self->client->i_laserCoolDown	= 0;
		self->client->i_decoyStopTime	= 0;
		self->client->qb_decoyMode		= qfalse;
		self->client->qb_laserPulseMode	= qfalse;
		self->client->qb_laserState		= qfalse;

		self->client->iLaserSetupFreezeTime = 0;
		break;

	case BOTS_INFILTRATOR:
		self->client->qb_disguiseMode		= qfalse;
		self->client->qb_stealMode			= qfalse;
		self->client->i_disguiseStopTime	= 0;
		ClientUserinfoChanged( self->s.clientNum );

		// force the loading of the players model to all clients
		//csfix: trap_SendServerCommand( -1, "loaddefered\n" );
		break;

	case BOTS_KAMIKAZEE:
		if (self->client->i_dettrap1)
			BOTS_dettrap(self->s.clientNum, 1);
		self->client->i_dettrap1			= 0;

		if (self->client->i_dettrap2)
			BOTS_dettrap(self->s.clientNum, 2);
		self->client->i_dettrap2			= 0;

		BOTS_settrap(self->s.clientNum, 0);
		self->client->qb_autorewireMode	= qfalse;
		self->client->qb_longrangeMode	= qfalse;
		self->client->qb_stickMode		= qfalse;
		self->client->i_rewireTime		= 0;
		break;

	case BOTS_NURSE:
		self->client->i_lastHealTime = level.time;
		break;

	case BOTS_SNIPER:
		if (self->lasersight)
			BOTS_sight(self->s.clientNum);
		if (self->client->invisible)
			BOTS_sniper_cloak(self->s.clientNum, qfalse);

		self->client->qb_cloakWalk	= qfalse;
		break;

	case BOTS_SOLDIER:
		{
			int conquer;
			int otherTeam;
			if (self->client->sess.sessionTeam == TEAM_RED)
				otherTeam = TEAM_BLUE;
			else
				otherTeam = TEAM_RED;

			conquer = BOTS_conquer_get(otherTeam);
			if (conquer == self->s.clientNum)
				BOTS_conquer_clear(otherTeam);

			self->client->qb_guide		= qfalse;
			self->client->qb_conquerMode= qfalse;
			self->client->qb_tagMode	= qfalse;
			self->client->i_tagNum		= 0;
			self->client->i_tagTime		= level.time;
			self->tagEnt				= NULL;
			self->tagEntbck				= NULL;
		}
		break;
	}
}


// This function is called from the CheckArmor function in g_combat.c

int bots_CheckArmor (gentity_t *ent, int ai_damage)
{
	gclient_t
		*client = ent->client;
	float
		armorProtect = ARMOR_PROTECTION;
	int
		armor = client->ps.stats[STAT_ARMOR];


	if ( armor <= 0)
		return 0;

	//balancefix:
	// 50% armor bonus for men
	/*
	if (ent->bots_team == TEAM_RED) {
		armorProtect *= 2;
	}

	// armor protection based on classlevel
	// note: lvl0 gets nothing
	switch (ent->client->pers.classlevel) {
	case 1:
		armorProtect += .02;
	case 2:
		armorProtect += .04;
	case 3:
		armorProtect += .06;
	case 4:
		armorProtect += .09;
	}


	// a level 4 male will already have a protect value of .75
	switch (ent->bots_class)
	{
		case BOTS_CAPTAIN:
			armorProtect += .15;
			break;
		case BOTS_BODYGUARD:
			armorProtect += .05;
			break;
		case BOTS_SNIPER:
			armorProtect -= .03;
			break;
		case BOTS_SOLDIER:
			armorProtect += .1;
			break;
		case BOTS_BERZERKER:
			armorProtect += .40;
			break;
		case BOTS_INFILTRATOR:
			armorProtect -= .03;
			break;
		case BOTS_KAMIKAZEE:
			// armorProtect += 0.0;
			break;
		case BOTS_NURSE:
			armorProtect -= .03;
			break;
		case BOTS_SCIENTIST:
			armorProtect -= .03;
			break;
	}
	*/

	ai_damage = ceil( ai_damage * armorProtect );

	// tbp: Armor cannot absorb more than its own value
	if (ai_damage > armor)
		ai_damage = armor;

	client->ps.stats[STAT_ARMOR] -= ai_damage;

	return ai_damage;
}


// Potentially add some code that would affect how the player walks, jumps, etc
// based on area he/she was hit

int bots_damageLocation (gentity_t *ent, float af_damage) {

	int		li_class;
	int		li_team;
	int		li_take;
	float	lf_modifier;

	li_class = ent->bots_class;
	li_team  = ent->bots_team;

	// This modifier might eventually be set based on the scientist research
	// level for the class instead of straight from the class level

	if (ent->client->pers.classlevel == 0)
		lf_modifier = 0.5;
	else if (ent->client->pers.classlevel == 1)
		lf_modifier = 0.75;
	else if (ent->client->pers.classlevel == 2)
		lf_modifier = 1.0;
	else if (ent->client->pers.classlevel == 3)
		lf_modifier = 1.25;
	else if (ent->client->pers.classlevel == 4)
		lf_modifier = 1.5;
	else
		lf_modifier = 0.5;

	// Men are harder to kill
	if (li_team == TEAM_RED) {
		af_damage = af_damage * 0.50;
	}

	li_take = 0;
	switch (li_class)
	{
		case BOTS_CAPTAIN:
			li_take = (int)(af_damage * (1.5 / lf_modifier));
			break;
		case BOTS_BODYGUARD:
			li_take = (int)(af_damage * (2 / lf_modifier));
			break;
		case BOTS_SNIPER:
			li_take = (int)(af_damage * (3 / lf_modifier));
			break;
		case BOTS_SOLDIER:
			li_take = (int)(af_damage * (1.5 / lf_modifier));
			break;
		case BOTS_BERZERKER:
			li_take = (int)(af_damage * (1 / lf_modifier));
			break;
		case BOTS_INFILTRATOR:
			li_take = (int)(af_damage * (2 / lf_modifier));
			break;
		case BOTS_KAMIKAZEE:
			li_take = (int)(af_damage * (2 / lf_modifier));
			break;
		case BOTS_NURSE:
			li_take = (int)(af_damage * (3 / lf_modifier));
			break;
		case BOTS_SCIENTIST:
			li_take = (int)(af_damage * (3 / lf_modifier));
			break;
	}

	if (li_take < 1)
		li_take = 1;

	return li_take;
}


/*
============
bots_G_LocationDamage
============
*/
int bots_G_LocationDamage(vec3_t point, gentity_t* targ, gentity_t* attacker, int take, int dflags) {
	vec3_t bulletPath;
	// vec3_t bulletAngle;

	int clientHeight;
	int clientFeetZ;
	int clientRotation;
	int bulletHeight;
	int bulletRotation;	// Degrees rotation around client.
				// used to check Back of head vs. Face
	int impactRotation;
	int h;

	float lf_take;


	// First things first.  If we're not damaging them, why are we here?
	if (!take)
		return 0;


	lf_take = (float)take;

	if (attacker->client) {
		lf_take += lf_take * (2 * bots_getTechLevel(attacker, SCI_WEAPON_DAMAGE) / 4);
	}


	// Point[2] is the REAL world Z. We want Z relative to the clients feet

	// Where the feet are at [real Z]
	clientFeetZ  = targ->r.currentOrigin[2] + targ->r.mins[2];
	// How tall the client is [Relative Z]
	clientHeight = targ->r.maxs[2] - targ->r.mins[2];
	// Where the bullet struck [Relative Z]
	bulletHeight = point[2] - clientFeetZ;

	if (dflags & DAMAGE_RADIUS) {
		// Old method for radius damage
		// Get a vector aiming from the client to the bullet hit
		VectorSubtract(targ->r.currentOrigin, point, bulletPath);
	}
	else {
		// New method for direct hits
		VectorSubtract(targ->r.currentOrigin, attacker->r.currentOrigin, bulletPath);
	}


	clientRotation = targ->client->ps.viewangles[YAW];
	bulletRotation = vectoyaw(bulletPath);

	impactRotation = clientRotation-bulletRotation;
	if (impactRotation < 360)
		impactRotation += 360;

	impactRotation += (180 + 45);	// just to make it easier to work with
	impactRotation %= 360;			// Keep it in the 0-359 range


	if (impactRotation < 90)
		targ->client->lasthurt_location = LOCATION_FRONT;
	else if (impactRotation < 180)
		targ->client->lasthurt_location = LOCATION_RIGHT;
	else if (impactRotation < 270)
		targ->client->lasthurt_location = LOCATION_BACK;
	else if (impactRotation < 360)
		targ->client->lasthurt_location = LOCATION_LEFT;
	else
		targ->client->lasthurt_location = LOCATION_NONE;


	h = clientHeight - bulletHeight;
	// The upper body never changes height, just distance from the feet
		if (bulletHeight > clientHeight - 2)
			targ->client->lasthurt_location |= LOCATION_HEAD;
		else if (bulletHeight > clientHeight - 8)
			targ->client->lasthurt_location |= LOCATION_FACE;
		else if (bulletHeight > clientHeight - 10)
			targ->client->lasthurt_location |= LOCATION_SHOULDER;
		else if (bulletHeight > clientHeight - 16)
			targ->client->lasthurt_location |= LOCATION_CHEST;
		else if (bulletHeight > clientHeight - 26)
			targ->client->lasthurt_location |= LOCATION_STOMACH;
		else if (bulletHeight > clientHeight - 29)
			targ->client->lasthurt_location |= LOCATION_GROIN;
		else if (bulletHeight < 4)
			targ->client->lasthurt_location |= LOCATION_FOOT;
		else
			// The leg is the only thing that changes size when you duck,
			// so we check for every other parts RELATIVE location, and
			// whats left over must be the leg.
			targ->client->lasthurt_location |= LOCATION_LEG;


		// Check the location ignoring the rotation info
		switch ( targ->client->lasthurt_location &
				~(LOCATION_BACK | LOCATION_LEFT | LOCATION_RIGHT | LOCATION_FRONT) )
		{
		case LOCATION_HEAD:
			lf_take *= 1.4;
			break;
		case LOCATION_FACE:
			if (targ->client->lasthurt_location & LOCATION_FRONT)
				lf_take *= 2.5; // Faceshots REALLY suck
			else
				lf_take *= 1.4;
			break;
		case LOCATION_SHOULDER:
			if (targ->client->lasthurt_location & (LOCATION_FRONT | LOCATION_BACK))
				lf_take *= 1.2; // Throat or nape of neck
			else
				lf_take *= 1.1; // Shoulders
			break;
		case LOCATION_CHEST:
			if (targ->client->lasthurt_location & (LOCATION_FRONT | LOCATION_BACK))
				lf_take *= 1.2; // Belly or back
			else
				lf_take *= 0.8; // Arms
			break;
		case LOCATION_STOMACH:
			lf_take *= 1.1;
			break;
		case LOCATION_GROIN:
			if (targ->client->lasthurt_location & LOCATION_FRONT)
				lf_take *= 1.2; // Groin shot
			else
				lf_take *= 0.8; // Butt shot.. lots of padding... :)
			break;
		case LOCATION_LEG:
			lf_take *= 0.6;
			break;
		case LOCATION_FOOT:
			lf_take *= 0.5;
			break;

	default:
		G_Printf("bots_G_LocationDamage: unkown body location\n");

	}

	take = bots_damageLocation( targ, lf_take );

	return take;
}


qboolean bots_GetClientSkin( gentity_t *ent, char *as_model )
{
	int		li_class;
	int		li_modifier;

	// Standard check to make sure the client is valid
	if (!ent->client)
		return qfalse;

	li_class = ent->bots_class;
	if (li_class == 0 || ent->bots_team == 0) {
		//(tbp) FIX: temp hack to get a proper icon for observers.
		Q_strncpyz( as_model, /*"orbb/default"*/ "bleeh!", MAX_QPATH );
		return qfalse;
	}

	li_modifier = ent->client->pers.classlevel;

	switch (li_class)
	{
		case BOTS_CAPTAIN:
			if (ent->bots_team == TEAM_BLUE) {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "fcaptain/l1fcaptain2", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "fcaptain/l2fcaptain2", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "fcaptain/l3fcaptain2", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "fcaptain/l4fcaptain2", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "fcaptain/fcaptain2", MAX_QPATH );
			}
			else {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "mcaptain/l1captain", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "mcaptain/l2captain", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "mcaptain/l3captain", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "mcaptain/l4captain", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "mcaptain/captain", MAX_QPATH );
			}
			break;
		case BOTS_BODYGUARD:
			if (ent->bots_team == TEAM_BLUE) {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "female/l1fbodyguard", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "female/l2fbodyguard", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "female/l3fbodyguard", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "female/l4fbodyguard", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "female/fbodyguard", MAX_QPATH );
			}
			else {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "male/l1bodyguard", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "male/l2bodyguard", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "male/l3bodyguard", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "male/l4bodyguard", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "male/bodyguard", MAX_QPATH );
			}
			break;
		case BOTS_SNIPER:
			if (ent->bots_team == TEAM_BLUE) {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "female/l1fsniper", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "female/l2fsniper", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "female/l3fsniper", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "female/l4fsniper", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "female/fsniper", MAX_QPATH );
			}
			else {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "male/l1sniper", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "male/l2sniper", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "male/l3sniper", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "male/l4sniper", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "male/sniper", MAX_QPATH );
			}
			break;
		case BOTS_SOLDIER:
			if (ent->bots_team == TEAM_BLUE) {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "female/l1fsoldier", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "female/l2fsoldier", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "female/l3fsoldier", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "female/l4fsoldier", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "female/fsoldier", MAX_QPATH );
			}
			else {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "male/l1soldier", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "male/l2soldier", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "male/l3soldier", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "male/l4soldier", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "male/soldier", MAX_QPATH );
			}
			break;
		case BOTS_BERZERKER:
			if (ent->bots_team == TEAM_BLUE) {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "female/l1fberzerker", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "female/l2fberzerker", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "female/l3fberzerker", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "female/l4fberzerker", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "female/fberzerker", MAX_QPATH );
			}
			else {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "male/l1berzerker", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "male/l2berzerker", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "male/l3berzerker", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "male/l4berzerker", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "male/berzerker", MAX_QPATH );
			}
			break;
		case BOTS_INFILTRATOR:
			if (ent->bots_team == TEAM_BLUE) {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "female/l1finfiltrator", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "female/l2finfiltrator", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "female/l3finfiltrator", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "female/l4finfiltrator", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "female/finfiltrator", MAX_QPATH );
			}
			else {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "male/l1infiltrator", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "male/l2infiltrator", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "male/l3infiltrator", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "male/l4infiltrator", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "male/infiltrator", MAX_QPATH );
			}
			break;
		case BOTS_KAMIKAZEE:
			if (ent->bots_team == TEAM_BLUE) {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "female/l1fkamikazee", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "female/l2fkamikazee", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "female/l3fkamikazee", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "female/l4fkamikazee", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "female/fkamikazee", MAX_QPATH );
			}
			else {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "male/l1kamikazee", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "male/l2kamikazee", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "male/l3kamikazee", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "male/l4kamikazee", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "male/kamikazee", MAX_QPATH );
			}
			break;
		case BOTS_NURSE:
			if (ent->bots_team == TEAM_BLUE) {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "female/l1fnurse", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "female/l2fnurse", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "female/l3fnurse", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "female/l4fnurse", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "female/fnurse", MAX_QPATH );
			}
			else {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "male/l1nurse", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "male/l2nurse", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "male/l3nurse", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "male/l4nurse", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "male/nurse", MAX_QPATH );
			}
			break;
		case BOTS_SCIENTIST:
			if (ent->bots_team == TEAM_BLUE) {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "fscientist/l1fscientist", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "fscientist/l2fscientist", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "fscientist/l3fscientist", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "fscientist/l4fscientist", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "fscientist/fscientist", MAX_QPATH );
			}
			else {
				/*
				if (li_modifier == 1)
					Q_strncpyz( as_model, "mscientist/l1scientist", MAX_QPATH );
				else if (li_modifier == 2)
					Q_strncpyz( as_model, "mscientist/l2scientist", MAX_QPATH );
				else if (li_modifier == 3)
					Q_strncpyz( as_model, "mscientist/l3scientist", MAX_QPATH );
				else if (li_modifier == 4)
					Q_strncpyz( as_model, "mscientist/l4scientist", MAX_QPATH );
				else
				*/
					Q_strncpyz( as_model, "mscientist/scientist", MAX_QPATH );
			}
			break;
		}

	return qtrue;
}


void ClientUserinfoChanged( int clientNum );

qboolean bots_ForceClientSkin( gentity_t *ent ) {

	gclient_t	*client;
	int			clientNum;


	// Standard check to make sure the client is valid
	if (!ent->client)
		return qfalse;

	if (ent->bots_class == 0)
		return qfalse;

	client = ent->client;
	clientNum = client - level.clients;

	ClientUserinfoChanged ( clientNum );

	return qtrue;
}


void BOTS_Add_Ammo(gentity_t *ent)
{
	if (!ent->client)
		return;

	ent->client->ps.ammo[WP_BFG] += BOTS_PACK_AMMO;
	ent->client->ps.ammo[WP_GRENADE_LAUNCHER] += BOTS_PACK_AMMO;
	ent->client->ps.ammo[WP_SHOTGUN] += BOTS_PACK_AMMO;
	ent->client->ps.ammo[WP_RAILGUN] += BOTS_PACK_AMMO;
	ent->client->ps.ammo[WP_ROCKET_LAUNCHER] += BOTS_PACK_AMMO;
	ent->client->ps.ammo[WP_PLASMAGUN] += BOTS_PACK_AMMO;
	ent->client->ps.ammo[WP_MACHINEGUN] += BOTS_PACK_AMMO;
	ent->client->ps.ammo[WP_LIGHTNING] += BOTS_PACK_AMMO;

	if (ent->client->ps.ammo[WP_BFG] > ent->client->pers.maxammo[WP_BFG])
		ent->client->ps.ammo[WP_BFG] = ent->client->pers.maxammo[WP_BFG];
	if (ent->client->ps.ammo[WP_GRENADE_LAUNCHER] > ent->client->pers.maxammo[WP_GRENADE_LAUNCHER])
		ent->client->ps.ammo[WP_GRENADE_LAUNCHER] = ent->client->pers.maxammo[WP_GRENADE_LAUNCHER];
	if (ent->client->ps.ammo[WP_SHOTGUN] > ent->client->pers.maxammo[WP_SHOTGUN])
		ent->client->ps.ammo[WP_SHOTGUN] = ent->client->pers.maxammo[WP_SHOTGUN];
	if (ent->client->ps.ammo[WP_RAILGUN] > ent->client->pers.maxammo[WP_RAILGUN])
		ent->client->ps.ammo[WP_RAILGUN] = ent->client->pers.maxammo[WP_RAILGUN];
	if (ent->client->ps.ammo[WP_ROCKET_LAUNCHER] > ent->client->pers.maxammo[WP_ROCKET_LAUNCHER])
		ent->client->ps.ammo[WP_ROCKET_LAUNCHER] = ent->client->pers.maxammo[WP_ROCKET_LAUNCHER];
	if (ent->client->ps.ammo[WP_PLASMAGUN] > ent->client->pers.maxammo[WP_PLASMAGUN])
		ent->client->ps.ammo[WP_PLASMAGUN] = ent->client->pers.maxammo[WP_PLASMAGUN];
	if (ent->client->ps.ammo[WP_MACHINEGUN] > ent->client->pers.maxammo[WP_MACHINEGUN])
		ent->client->ps.ammo[WP_MACHINEGUN] = ent->client->pers.maxammo[WP_MACHINEGUN];
	if (ent->client->ps.ammo[WP_LIGHTNING] > ent->client->pers.maxammo[WP_LIGHTNING])
		ent->client->ps.ammo[WP_LIGHTNING] = ent->client->pers.maxammo[WP_LIGHTNING];

	ent->client->ps.stats[STAT_ARMOR] += BOTS_PACK_ARMOR;
	if (ent->client->ps.stats[STAT_ARMOR] > ent->client->ps.stats[STAT_MAX_ARMOR])
		ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_ARMOR];
}


void bots_SetMaxValues( gentity_t *ent ) {

	int		li_class;
	int		li_team;
	int		li_base;
	float	lf_modifier;

	li_class = ent->bots_class;
	li_team  = ent->bots_team;

	lf_modifier = ent->client->pers.classlevel * 1.0;
	if (lf_modifier < 1.0)
		lf_modifier = 1.0;

	lf_modifier = 1.0 + lf_modifier / 4 + bots_getTechLevel(ent, SCI_GENETIC_STRENGTH) / 4;

	if (li_team == TEAM_RED)
		li_base = 10;
	else
		li_base = 5;

	ent->client->pers.maxammo[WP_BFG] = 50;
	ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = li_base;
	ent->client->pers.maxammo[WP_SHOTGUN] = li_base;
	ent->client->pers.maxammo[WP_RAILGUN] = li_base;
	ent->client->pers.maxammo[WP_ROCKET_LAUNCHER] = li_base;
	ent->client->pers.maxammo[WP_MACHINEGUN] = li_base;
    ent->client->pers.maxammo[WP_PLASMAGUN] = ent->client->pers.maxammo[WP_BFG];
    ent->client->pers.maxammo[WP_LIGHTNING] = ent->client->pers.maxammo[WP_BFG];

	switch (li_class)
	{
		case BOTS_CAPTAIN:
			if (li_team == TEAM_RED) {
				ent->client->pers.maxammo[WP_BFG] = (int)(200 * lf_modifier);
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = 40;
				ent->client->ps.stats[STAT_MAX_HEALTH] = 150;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 550;
			}
			else {
				ent->client->pers.maxammo[WP_BFG] = (int)(200 * lf_modifier);
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = 36;
				ent->client->ps.stats[STAT_MAX_HEALTH] = 250;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 275;
			}
			break;
		case BOTS_BODYGUARD:
			if (li_team == TEAM_RED) {
				ent->client->pers.maxammo[WP_SHOTGUN] = (int)(90 * lf_modifier);
				ent->client->pers.maxammo[WP_BFG] = (int)(100 * lf_modifier);
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = 40;
				ent->client->ps.stats[STAT_MAX_HEALTH] = 150;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 470;
			}
			else {
				ent->client->pers.maxammo[WP_SHOTGUN] = (int)(70 * lf_modifier);
				ent->client->pers.maxammo[WP_BFG] = (int)(75 * lf_modifier);
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = 36;
				ent->client->ps.stats[STAT_MAX_HEALTH] = 250;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 235;
			}
			break;
		case BOTS_SNIPER:
			if (li_team == TEAM_RED) {
				ent->client->pers.maxammo[WP_BFG] = (int)(150 * lf_modifier);
				ent->client->pers.maxammo[WP_RAILGUN] = (int)(75 * lf_modifier);
				ent->client->ps.stats[STAT_MAX_HEALTH] = 150;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 270;
			}
			else {
				ent->client->pers.maxammo[WP_BFG] = (int)(100 * lf_modifier);
				ent->client->pers.maxammo[WP_RAILGUN] = (int)(55 * lf_modifier);
				ent->client->ps.stats[STAT_MAX_HEALTH] = 250;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 135;
			}
			break;
		case BOTS_SOLDIER:
			if (li_team == TEAM_RED) {
				ent->client->pers.maxammo[WP_ROCKET_LAUNCHER] = (int)(60 * lf_modifier);
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = 5;
				ent->client->ps.stats[STAT_MAX_HEALTH] = 150;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 550;
			}
			else {
				ent->client->pers.maxammo[WP_ROCKET_LAUNCHER] = (int)(35 * lf_modifier);
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = 2;
				ent->client->ps.stats[STAT_MAX_HEALTH] = 250;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 275;
			}
			break;
		case BOTS_BERZERKER:
			if (li_team == TEAM_RED) {
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = (int)(30 * lf_modifier);
				ent->client->ps.stats[STAT_MAX_HEALTH] = 175;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 700;
			}
			else {
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = (int)(18 * lf_modifier);
				ent->client->ps.stats[STAT_MAX_HEALTH] = 275;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 350;
			}
			break;
		case BOTS_INFILTRATOR:
			if (li_team == TEAM_RED) {
				ent->client->pers.maxammo[WP_BFG] = (int)(200 * lf_modifier);
				ent->client->pers.maxammo[WP_PLASMAGUN] = (int)(200 * lf_modifier);
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = 8;
				ent->client->ps.stats[STAT_MAX_HEALTH] = 150;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 270;
			}
			else {
				ent->client->pers.maxammo[WP_BFG] = (int)(150 * lf_modifier);
				ent->client->pers.maxammo[WP_PLASMAGUN] = (int)(150 * lf_modifier);
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = 4;
				ent->client->ps.stats[STAT_MAX_HEALTH] = 250;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 135;
			}
			break;
		case BOTS_KAMIKAZEE:
			if (li_team == TEAM_RED) {
				ent->client->pers.maxammo[WP_BFG] = (int)(160 * lf_modifier);
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = (int)(60 * lf_modifier);
				ent->client->ps.stats[STAT_MAX_HEALTH] = 175;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 240;
			}
			else {
				ent->client->pers.maxammo[WP_BFG] = (int)(120 * lf_modifier);
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = (int)(45 * lf_modifier);
				ent->client->ps.stats[STAT_MAX_HEALTH] = 275;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 120;
			}
			break;
		case BOTS_NURSE:
			if (li_team == TEAM_RED) {
				ent->client->pers.maxammo[WP_BFG] = (int)(180 * lf_modifier);
				ent->client->pers.maxammo[WP_MACHINEGUN] = (int)(250 * lf_modifier);
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = 8;
				ent->client->ps.stats[STAT_MAX_HEALTH] = 175;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 210;
			}
			else {
				ent->client->pers.maxammo[WP_BFG] = (int)(120 * lf_modifier);
				ent->client->pers.maxammo[WP_MACHINEGUN] = (int)(200 * lf_modifier);
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = 4;
				ent->client->ps.stats[STAT_MAX_HEALTH] = 275;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 105;
			}
			break;
		case BOTS_SCIENTIST:
			if (li_team == TEAM_RED) {
				ent->client->pers.maxammo[WP_BFG] = (int)(200 * lf_modifier);
				ent->client->pers.maxammo[WP_LIGHTNING] = (int)(250 * lf_modifier);
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = 12;
				ent->client->ps.stats[STAT_MAX_HEALTH] = 175;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 200;
			}
			else {
				ent->client->pers.maxammo[WP_BFG] = (int)(150 * lf_modifier);
				ent->client->pers.maxammo[WP_LIGHTNING] = (int)(200 * lf_modifier);
				ent->client->pers.maxammo[WP_GRENADE_LAUNCHER] = 8;
				ent->client->ps.stats[STAT_MAX_HEALTH] = 275;
				ent->client->ps.stats[STAT_MAX_ARMOR] = 100;
			}
			break;
	}

	ent->client->ps.stats[STAT_MAX_HEALTH] = ent->client->ps.stats[STAT_MAX_HEALTH] + (int)(200 * bots_getTechLevel(ent, SCI_GENETIC_HEALTH) / 4);
	ent->client->ps.stats[STAT_MAX_ARMOR] = ent->client->ps.stats[STAT_MAX_ARMOR] + (int)(200 * bots_getTechLevel(ent, SCI_ARMOR_ADVANCED) / 4);
}

qboolean BOTS_enforce_class_limits(gentity_t* ent, int ai_class)
{
	gentity_t*	captain = bots_getCaptain(ent->bots_team);
	int			numPlayersInClass = 0;
	int			i;

	// no captain, no limits
	if (!captain)
		return qtrue;

	if (ent == captain)
		return qtrue;

	if (ent->bots_class == BOTS_SCIENTIST)
		return qtrue;

	// don't enforce limits on players that are promoted.
	// The captain can demote them if he wants them to be removed upon respawn
	if (ent->client->pers.classlevel > 0)
		return qtrue;

	for(i = 0; i < level.maxclients; i++)
	{
		gentity_t* player = &g_entities[i];

		if (!ent->client)
			continue;
		if (!OnSameTeam (ent, player))
			continue;
		if (player->bots_class == ai_class)
			numPlayersInClass++;
	}

	// still room, it's ok for the player to be this class
	if (numPlayersInClass <= captain->client->pers.classLimit[ai_class])
		return qtrue;
	else
		return qfalse;
}


void bots_SetSpeed( gentity_t *ent ) {

	int		li_class;
	int		li_team;
	float	lf_modifier;

	li_class = ent->bots_class;
	li_team  = ent->bots_team;

	// Credits to QuakeStyle and (NOBODY): Change our movement speed!
	if (li_team == TEAM_RED) {
		lf_modifier = 0.95;
	}
	else {
		//balancefix: lf_modifier = 1.1;
		lf_modifier = 1.05;
	}

	lf_modifier += 0.1 * bots_getTechLevel(ent, SCI_GENETIC_SPEED);

	switch (li_class)
	{
		case BOTS_BERZERKER:
			lf_modifier *= 0.8;
			break;
		case BOTS_CAPTAIN:
			lf_modifier *= 0.9;
			break;
		case BOTS_SOLDIER:
			lf_modifier *= 0.9;
			break;
		case BOTS_BODYGUARD:
			lf_modifier *= 1.0;
			break;
		case BOTS_KAMIKAZEE:
			lf_modifier *= 1.0;
			break;
		case BOTS_SNIPER:
			lf_modifier *= 1.1;
			break;
		case BOTS_INFILTRATOR:
			lf_modifier *= 1.1;
			break;
		case BOTS_NURSE:
			lf_modifier *= 1.1;
			break;
		case BOTS_SCIENTIST:
			lf_modifier *= 1.1;
			break;
	}

	ent->client->pers.speed_modifier = lf_modifier;
}


void bots_SetGravity( gentity_t *ent ) {

	int		li_class;
	int		li_team;
	float	lf_modifier;

	li_class = ent->bots_class;
	li_team  = ent->bots_team;

	// Credits to QuakeStyle and (NOBODY): Change our movement speed!
	if (li_team == TEAM_RED) {
		lf_modifier = 1.2;
	}
	else {
		lf_modifier = 0.8;
	}

	lf_modifier -= 0.1 * bots_getTechLevel(ent, SCI_GENETIC_JUMP);

	switch (li_class)
	{
		case BOTS_CAPTAIN:
			lf_modifier *= 1.1;
			break;
		case BOTS_BODYGUARD:
			lf_modifier *= 1.0;
			break;
		case BOTS_SNIPER:
			lf_modifier *= 0.9;
			break;
		case BOTS_SOLDIER:
			lf_modifier *= 1.1;
			break;
		case BOTS_BERZERKER:
			lf_modifier *= 1.2;
			break;
		case BOTS_INFILTRATOR:
			lf_modifier *= 0.9;
			break;
		case BOTS_KAMIKAZEE:
			lf_modifier *= 1.0;
			break;
		case BOTS_NURSE:
			lf_modifier *= 0.9;
			break;
		case BOTS_SCIENTIST:
			lf_modifier *= 0.9;
			break;
	}

	ent->client->pers.gravity_modifier = lf_modifier;
}

// We need to cache the weapon damage & fire rate upgrades so they are only changed on respawn (if needed)
void bots_CacheWeapon(gentity_t *ent) {
	ent->fCachedFireRate		= bots_getTechLevel(ent, SCI_WEAPON_FIRERATE);
	ent->fCachedWeaponDamage	= bots_getTechLevel(ent, SCI_WEAPON_DAMAGE);

	G_LogPrintf("Team %i Class %i WD %i FR %i\n",
		ent->bots_team,
		ent->bots_class,
		(int)ent->fCachedWeaponDamage*100,(int)ent->fCachedFireRate*100);
}


qboolean bots_Respawn( gentity_t *ent ) {
	qboolean done = qfalse;
	int li_class  = ent->bots_class;

	// check to see if the captain imposed limits
	if (bots_getCaptain(ent->bots_team)) {
	while (1)
	{
		done = BOTS_enforce_class_limits(ent, li_class);
		if (done)
			break;

		li_class++;

		if (li_class > 8)
			li_class = 2;

		// captain is being an asshole... assign the player to the
		// class they initialy selected
		if (li_class == ent->bots_class)
			break;
	}

	// if they are supposed to change, change them
		if (li_class != ent->bots_class) {
		bots_ChangeClass (ent, li_class);
		BOTS_cp_console(ent->s.clientNum, "The class you chose is full.");
	}
	}

	bots_SetMaxValues( ent );
	bots_InitClass( ent );
	bots_InitClient( ent );
	bots_SetSpeed( ent );
	bots_SetGravity( ent );

	bots_CacheWeapon(ent);

	bots_ForceClientSkin( ent );

	if (ent->bots_class != BOTS_BERZERKER) {
		ent->client->qb_rageMode = qtrue;
		ent->client->i_rageStopTime = level.time + 5000;
		ent->client->ps.powerups[PW_FLIGHT] = ent->client->i_rageStopTime;
	}

	return qtrue;
}


void ClientBegin( int clientNum );

qboolean bots_ChangeClass( gentity_t *ent, int ai_class )
{
	char		ls_class[30];
	gclient_t	*client;
	int			clientNum;
	gentity_t	*player;

	qboolean	done = qfalse;
	int			startClassNum = ai_class;

	int			i;
	char*		keyname;

//	qboolean	newmap;

	int li_score, li_hits, li_rank, li_spawn_count, li_reward_count;
	int li_reward, li_killed, li_impresive_count, li_excellent_count;
	int li_gauntlet_frag_count, li_accuracy_shots, li_accuracy_hits, li_frags;


	// Standard check to make sure the client is valid
	if (!ent->client)
		return qfalse;
	else {
		// Shortcuts :)
		client		= ent->client;
		clientNum	= client - level.clients;
	}

	BOTS_return_PromoPoints(ent);

	if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
		// Kill him (makes sure he loses flags, etc)
		ent->flags &= ~FL_GODMODE;
		ent->client->ps.stats[STAT_HEALTH] = ent->health = 0;
		player_die (ent, ent, ent, 100000, MOD_SUICIDE);
	}
// BEGIN bots - tbp
	if ((ent->bots_team != TEAM_RED) && (ent->bots_team != TEAM_BLUE)) {
		// Not valid yet
		// Let him on the spectator team and ask the UI to draw the menu selection
		//
		SetTeam(ent,"s");
		BOTS_cp_console(clientNum,"Please choose a team first\n");
		trap_SendServerCommand(clientNum, "rejoin_data_complete");	// Rejoin sequence (team picking...etc...)
		return qfalse;
	}
	else {
		client->sess.sessionTeam  = ent->bots_team;
	}
// END bots - tbp

	// Call think function before we change class to clean up existing crapola
	bots_ClientThink_real(ent, NULL);

	// Clean up things that do not get cleaned up when a player dies.
	BOTS_ClientDisconnect(ent);

   	ent->client->pers.teamState.state = TEAM_BEGIN;

	// Initialize the new class
	if (ent->client->sess.sessionTeam != TEAM_BLUE && ent->client->sess.sessionTeam != TEAM_RED)
		ent->client->sess.sessionTeam = TEAM_BLUE;

	if (ent->client->sess.sessionTeam == ent->bots_team &&
		ent->client->pers.classlevel > 0)
		ent->client->pers.classlevel = 1;
	else
		ent->client->pers.classlevel = 0;

	if (ai_class < 1 || ai_class > 9)
		ai_class = BOTS_BODYGUARD;

	if (ai_class == BOTS_CAPTAIN) {
		player = bots_getCaptain(ent->client->sess.sessionTeam);
		if (player && player != ent) {
			BOTS_cp_console(ent->s.clientNum, "Your team already has a Captain.");
			ai_class = BOTS_BODYGUARD;
		}
		else if (ent->client->pers.impeached) {
			BOTS_cp_console(ent->s.clientNum, "You cannot be Captain again. You were impeached.");
			ai_class = BOTS_BODYGUARD;
		}
	}
	else if (ai_class == BOTS_SCIENTIST) {
		player = bots_getScientist(ent->client->sess.sessionTeam);
		if (player && player != ent) {
			BOTS_cp_console(ent->s.clientNum, "Your team already has a Scientist.");
			ai_class = BOTS_BODYGUARD;
		}
	}

	// check to see if the captain has put any limits upon the class being selected.
	while (ai_class != BOTS_SCIENTIST)
	{
		done = BOTS_enforce_class_limits(ent, ai_class);
		if (done)
			break;

		ai_class++;

		if (ai_class > 8)
			ai_class = 2;

		// captain is being an asshole... assign the player to the
		// class they initialy selected
		if (ai_class == startClassNum)
			break;
	}

	// tell him he was changed
	if (ai_class != startClassNum)
		BOTS_cp_console(ent->s.clientNum, "The class you chose is full.");

	// clean up poisoned health if it is a nurse going to a different class
	if (ent->bots_class == BOTS_NURSE && ai_class != BOTS_NURSE)
		BOTS_remove_poison(ent);

	ent->bots_class = ai_class;

	// he starts at 'base'
	// they go to the end of the line for tournements
	if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
		ent->client->sess.spectatorTime = level.time;
	}

	ent->client->sess.sessionTeam = ent->bots_team;
	ent->client->sess.spectatorState = SPECTATOR_NOT;
	ent->client->sess.spectatorClient = 0;
	ent->client->sess.sessionClass = ent->bots_class;

	// get and distribute relevent parameters
	ClientUserinfoChanged( clientNum );

	// save persistant info
	li_score = ent->client->ps.persistant[PERS_SCORE];
	li_hits = ent->client->ps.persistant[PERS_HITS];
	li_rank = ent->client->ps.persistant[PERS_RANK];
	li_spawn_count = ent->client->ps.persistant[PERS_SPAWN_COUNT];
//	li_reward_count = ent->client->ps.persistant[PERS_REWARD_COUNT];
//	li_reward = ent->client->ps.persistant[PERS_REWARD];
	li_killed = ent->client->ps.persistant[PERS_KILLED];
	li_impresive_count = ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT];
	li_excellent_count = ent->client->ps.persistant[PERS_EXCELLENT_COUNT];
	li_gauntlet_frag_count = ent->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT];
//	li_accuracy_shots = ent->client->ps.persistant[PERS_ACCURACY_SHOTS];
//	li_accuracy_hits = ent->client->ps.persistant[PERS_ACCURACY_HITS];
	li_frags = ent->client->ps.persistant[PERS_FRAGS];

	ClientBegin( clientNum );

	// restore persistant info
	ent->client->ps.persistant[PERS_SCORE] = li_score;
	ent->client->ps.persistant[PERS_HITS] = li_hits;
	ent->client->ps.persistant[PERS_RANK] = li_rank;
	ent->client->ps.persistant[PERS_SPAWN_COUNT] = li_spawn_count;
//	ent->client->ps.persistant[PERS_REWARD_COUNT] = li_reward_count;
	//ent->client->ps.persistant[PERS_REWARD] = li_reward;
	ent->client->ps.persistant[PERS_KILLED] = li_killed;
	ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT] = li_impresive_count;
	ent->client->ps.persistant[PERS_EXCELLENT_COUNT] = li_excellent_count;
	ent->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT] = li_gauntlet_frag_count;
	//ent->client->ps.persistant[PERS_ACCURACY_SHOTS] = li_accuracy_shots;
//	ent->client->ps.persistant[PERS_ACCURACY_HITS] = li_accuracy_hits;
	ent->client->ps.persistant[PERS_FRAGS] = li_frags;

	switch (ai_class)
	{
	case BOTS_CAPTAIN:
		{
			// initialize the class limits array
			int i;
			for (i = 1; i < 9; i++)
				ent->client->pers.classLimit[i] = 999;

			// set impeachvote to qfalse for all team mates
			for (i = 0; i < level.maxclients; i++)
			{
				gentity_t* player = &g_entities[i];

				if (!player->client)
					continue;
				if (!OnSameTeam(ent, player))
					continue;

				player->client->pers.impeachvote = qfalse;
			}

			strcpy(ls_class, CLASS1);
		}
		break;
	case BOTS_BODYGUARD:
		strcpy(ls_class, CLASS2);
		break;
	case BOTS_SNIPER:
		strcpy(ls_class, CLASS3);
		break;
	case BOTS_SOLDIER:
		strcpy(ls_class, CLASS4);
		break;
	case BOTS_BERZERKER:
		strcpy(ls_class, CLASS5);
		break;
	case BOTS_INFILTRATOR:
		strcpy(ls_class, CLASS6);
		break;
	case BOTS_KAMIKAZEE:
		strcpy(ls_class, CLASS7);
		break;
	case BOTS_NURSE:
		strcpy(ls_class, CLASS8);
		break;
	case BOTS_SCIENTIST:
		strcpy(ls_class, CLASS9);
		break;
	}

	bots_SetMaxValues( ent );
	bots_InitClass( ent );
	bots_InitClient( ent );
	bots_SetSpeed( ent );
	bots_SetGravity( ent );

	bots_CacheWeapon( ent );

	// initialize data that doesn't get reset each respawn
	ent->alarm1				= NULL;
	ent->alarm2				= NULL;
	ent->alarm3				= NULL;
	ent->alarm1name[0]		= 0;
	ent->alarm2name[0]		= 0;
	ent->alarm3name[0]		= 0;
	ent->flagalarm1name[0]	= 0;
	ent->flagalarm2name[0]	= 0;
	ent->flagalarm3name[0]	= 0;
	ent->alarmmsg[0]		= 0;
	ent->client->pers.scoutmsg	= qtrue;
	ent->client->pers.techmsg	= qtrue;
	ent->client->pers.alarmmsg	= qtrue;


	ent->client->pers.forcekill = 0;

	// find the promotion key for the team
	if (ent->bots_team == TEAM_RED)
		keyname = "promotion_key_red";
	else
		keyname = "promotion_key_blue";

	ent->promotionkey = NULL;
	for (i = 0; i < level.num_entities; i++)
	{
		gentity_t* i_ent = &g_entities[i];

		if (!i_ent->classname)
			continue;

		if (Q_stricmp(i_ent->classname, keyname) == 0)
		{
			ent->promotionkey = i_ent;
			break;
		}
	}

	// find the tech key for the team
	if (ent->bots_team == TEAM_RED)
		keyname = "tech_key_red";
	else
		keyname = "tech_key_blue";

	ent->techkey = NULL;
	for (i = 0; i < level.num_entities; i++)
	{
		gentity_t* i_ent = &g_entities[i];

		if (!i_ent->classname)
			continue;

		if (Q_stricmp(i_ent->classname, keyname) == 0)
		{
			ent->techkey = i_ent;
			break;
		}
	}

	if (!ent->client->pers.isbot) {
		// force the loading of the players model to all clients
		//csfix: trap_SendServerCommand( -1, "loaddefered\n" );

		// execute class specific config
		BOTS_myclass(clientNum);
	}

	BOTS_console( -1, va("%s" S_COLOR_WHITE " switched to %s.", ent->client->pers.netname, ls_class));
	return qtrue;
}

void BOTS_ClientDisconnect(gentity_t *self)
{
	/*
	switch(ent->bots_class)
	{
	case BOTS_SNIPER:
		BOTS_alarmkill(ent->s.clientNum, qtrue);

		if (ent->lasersight)
			BOTS_sight(ent->s.clientNum);
		break;
	case BOTS_NURSE:
		BOTS_remove_poison(ent);
		break;
	default:
		break;
	}
	*/
// BEGIN bots - tbp
	// Crapola clean up. Some stuff was left with the old method (and now bots_class can be cleared before reaching that code)
	// Motto: be brutal
	int			i;
	gentity_t	*crap = g_entities;


	// Zerk
	if (self->client->trooptransport)
		BOTS_BerzerkerReleaseOne(self);

	// Laser, sight, proxies, grenades and the like
	// +poison
	for (i = 0; i < level.num_entities; crap++,i++)
	{
		if (!crap)
			continue;

		if (crap->bg_protector == self) {
			crap->bg_protector = NULL;
			continue;
		}

		if (crap->item && crap->item->giType == IT_HEALTH && crap->poisoner == self) {
			crap->poisoner		= NULL;
			crap->last_poisoner = NULL;
			continue;
		}

		if (crap->parent != self)
			continue;

		if (!crap->classname)
			continue;

		if (Q_stricmp (crap->classname, "proxy_grenade")	&&
			Q_stricmp (crap->classname, "decoy_grenade")	&&
			Q_stricmp (crap->classname, "laser_mount")		&&
			Q_stricmp (crap->classname, "bots_laser")		&&
			Q_stricmp (crap->classname, "alarm")			&&
			Q_stricmp (crap->classname, "sight")

		)
			continue;

		G_FreeEntity(crap);
		// grenade->think = G_ExplodeMissile;
		// grenade->nextthink = level.time + 400;
	}

	// Sniper
	self->lasersight		= NULL;
	// BG
	self->laserbeam			= NULL;
	self->laserbeam2		= NULL;
	self->laserbase			= NULL;
	self->laserbase2		= NULL;
	self->bg_protector		= NULL;
	self->team_protect		= NULL;


// END bots - tbp

	bots_InitClient(self);
}


void SetTeam( gentity_t *ent, char *s );

qboolean bots_ChangeTeam( gentity_t *ent, char *as_team ) {
	if ( !Q_stricmp( as_team, "Men" ) ) {
		SetTeam( ent, "red");
	} else if ( !Q_stricmp( as_team, "Women" ) ) {
		SetTeam( ent, "blue");
	} else if ( !Q_stricmp( as_team, "Random" ) ) {
		SetTeam( ent, "random");
	} else if ( !Q_stricmp( as_team, "Observer" ) ) {
		SetTeam( ent, "spectator");
	} else {
		return qfalse;
	}

	return qtrue;
}


void BOTS_transferPromos(int ai_from, int ai_to);

void BOTS_touch_promo_key(gentity_t* key, gentity_t* ent)
{
	qboolean	enemy_key;
	// gentity_t*	captain = NULL;
	int			i;
	int			team;

	if (!ent->client)
		return;
	if (ent->client->sess.sessionTeam == TEAM_SPECTATOR)
		return;

	// figure out what key we just touched
	if (ent->promotionkey == key)
		enemy_key = qfalse;
	else if (ent->client->sess.sessionTeam == TEAM_RED)
	{
		if (Q_stricmp(key->classname, "promotion_key_red") == 0)
			enemy_key = qfalse;
		else
			enemy_key = qtrue;
	}
	else
	{
		if (Q_stricmp(key->classname, "promotion_key_blue") == 0)
			enemy_key = qfalse;
		else
			enemy_key = qtrue;
	}

	// if the key is ours and we are not a captain, do nothing
	if (!enemy_key)
	{
		if (ent->bots_class != BOTS_CAPTAIN)
			return;
	}
	else
	{
		char*	key_color;

		// this seems redundant...
		if (Q_stricmp(key->classname, "promotion_key_red") == 0)
			key_color = "Male";
		else
			key_color = "Female";

		// promote the player
		ent->client->pers.classlevel++;
		if (ent->client->pers.classlevel > 4)
			ent->client->pers.classlevel = 4;

		if (ent->bots_class == BOTS_INFILTRATOR) {
			if (ent->client->sess.sessionTeam == TEAM_RED)
				BOTS_transferPromos(TEAM_BLUE, TEAM_RED);
			else
				BOTS_transferPromos(TEAM_RED, TEAM_BLUE);

			BOTS_cp_console(-1, va("%s has stolen the %s promotion key.",
									ent->client->pers.netname, key_color));
			bots_AddScore(ent, BOTS_GET_PROMOKEY, BOTS_SCORE_POINT,ent->r.currentOrigin);
		}
		else
		{
			int* promotech;
			int  other_team;

			if (ent->client->sess.sessionTeam == TEAM_RED)
				other_team = TEAM_BLUE;
			else
				other_team = TEAM_RED;

			promotech = BOTS_get_promotech_points(other_team);
			promotech[0] = 0;
			BOTS_set_promotech_points(other_team, promotech);

			BOTS_cp_console(-1, va("%s destroyed the %s promotion key.",
									ent->client->pers.netname, key_color));

			// Demote enemy players greater than level 1
			for (i = 0; i < level.maxclients; i++)
			{
				gentity_t* player = &g_entities[i];

				if (!player->client)
					continue;
				if (OnSameTeam (ent, player))
					continue;
				if (player->client->pers.classlevel <= 1)
					continue;

				player->client->pers.classlevel--;
				BOTS_cp_console(player - g_entities, va("You have been demoted to level %i!", player->client->pers.classlevel));
			}
			bots_AddScore(ent, BOTS_GET_PROMOKEY, BOTS_SCORE_POINT,ent->r.currentOrigin );
		}
	}

	// find the captain and return his key
	if (ent->promotionkey == key)
		team = ent->client->sess.sessionTeam;
	else if (Q_stricmp(key->classname, "promotion_key_red") == 0)
		team = TEAM_RED;
	else
		team = TEAM_BLUE;

	// NULL out team key
	for (i = 0; i < level.maxclients; i++)
	{
		gentity_t* player = &g_entities[i];

		if (!player->client)
			continue;
		if (player->client->sess.sessionTeam == TEAM_SPECTATOR)
			continue;
		if (player->client->sess.sessionTeam != team)
			continue;

		player->promotionkey = NULL;
	}

	G_AddEvent( ent, EV_ITEM_PICKUP, key->s.modelindex );
	G_FreeEntity(key);
}


void BOTS_transferTech(int ai_from, int ai_to);

void BOTS_touch_tech_key(gentity_t* key, gentity_t* ent)
{
	gentity_t*	scientist = NULL;
	int			i;

	int		keyColor;
	char*	keyName;

	if (!ent->client)
		return;
	if (ent->client->sess.sessionTeam == TEAM_SPECTATOR)
		return;

	if (Q_stricmp(key->classname, "tech_key_red") == 0)
	{
		keyName  = "Male";
		keyColor = TEAM_RED;
	}
	else
	{
		keyName  = "Female";
		keyColor = TEAM_BLUE;
	}

	// if the key is ours and we are not a scientist, do nothing
	if (ent->client->sess.sessionTeam == keyColor)
	{
		if (ent->bots_class != BOTS_SCIENTIST)
			return;
	}
	else
	{
		scientist = bots_getScientist(keyColor);

		if (ent->bots_class == BOTS_INFILTRATOR)
		{
			// transfer tech points from -> to
			if (keyColor == TEAM_RED)
				BOTS_transferTech(TEAM_RED, TEAM_BLUE);
			else
				BOTS_transferTech(TEAM_BLUE, TEAM_RED);

			BOTS_cp_console(-1, va("%s has stolen the %s tech key.",
									ent->client->pers.netname, keyName));
			bots_AddScore(ent, BOTS_GET_TECHKEY, BOTS_SCORE_POINT,ent->r.currentOrigin );
		}
		else
		{
			int*		promotech;

			BOTS_cp_console(-1, va("%s destroyed the %s tech key.",
									ent->client->pers.netname, keyName));
			// Demote enemy scientist
			if (scientist)
			{
				if (scientist->client->pers.classlevel > 0)
				{
					scientist->client->pers.classlevel--;
					BOTS_cp_console(scientist - g_entities, va("You have been demoted to level %i!", scientist->client->pers.classlevel));
				}
			}

			// whoever got their key destroyed loses all tech points
			promotech = BOTS_get_promotech_points(keyColor);
			promotech[1] = 0;
			BOTS_set_promotech_points(keyColor, promotech);

			// remove a tech level across the board for the team that got their tech key
			// destroyed
			BOTS_tech_stripe(keyColor);

			bots_AddScore(ent, BOTS_GET_TECHKEY, BOTS_SCORE_POINT,ent->r.currentOrigin);
		}
	}

	// NULL out team key
	for (i = 0; i < level.maxclients; i++)
	{
		gentity_t* player = &g_entities[i];

		if (!player->client)
			continue;
		if (player->client->sess.sessionTeam != keyColor)
			continue;

		player->techkey = NULL;
	}

	G_AddEvent( ent, EV_ITEM_PICKUP, key->s.modelindex );
	G_FreeEntity(key);
}

qboolean bots_ClientThink_real( gentity_t *ent, usercmd_t *ucmd ) {

	int		li_blood;
	int		li_class;
	int		li_team;

//	trace_t		tr;
//	gentity_t	*tent;
//	gentity_t	*traceEnt;
//	vec3_t		forward, right, up, muzzle, end;

	// Standard check to make sure the client is valid
	if (!ent->client)
		return qfalse;

	ent->client->ps.stats[STAT_HEALTH] = ent->health;

	if (ent->health <= 0)
	{
		// general
		ent->bg_protector = NULL;
		return qtrue;
	}

	li_class = ent->bots_class;
	li_team  = ent->bots_team;

	ent->client->ps.gravity *= ent->client->pers.gravity_modifier;
	ent->client->ps.speed *= ent->client->pers.speed_modifier;

	switch (li_class)
	{
	case BOTS_CAPTAIN:
		BOTS_ClientThink_captain(ent);
		break;
	case BOTS_BODYGUARD:
		BOTS_ClientThink_bodyguard(ent);
		break;
	case BOTS_SNIPER:
		BOTS_ClientThink_sniper(ent, ucmd);
		break;
	case BOTS_SOLDIER:
		BOTS_ClientThink_soldier(ent);
		break;
	case BOTS_BERZERKER:
		BOTS_ClientThink_berzerker(ent);
		break;
	case BOTS_INFILTRATOR:
		BOTS_ClientThink_infiltrator(ent);
		break;
	case BOTS_KAMIKAZEE:
		BOTS_ClientThink_kamikazee(ent);
		break;
	case BOTS_NURSE:
		BOTS_ClientThink_nurse(ent);
		break;
	case BOTS_SCIENTIST:
		BOTS_ClientThink_scientist(ent);
		break;
	}

	// Check bleeding
	if (ent->health < 75 && ent->health > 0) {
		// Don't bleed every time we get here
		if ((rand() & ent->health) < 2)
		{
			// We don't need to play the sound every time blood is displayed
			if ((rand() & 50) < 2)
			{
				// play an apropriate pain sound
				if ( (level.time > ent->pain_debounce_time) && !(ent->flags & FL_GODMODE) ) {
					ent->pain_debounce_time = level.time + 700;
					G_AddEvent( ent, EV_PAIN, ent->health );
					ent->client->ps.damageEvent++;
				}
			}

			li_blood = (int)((1 / ent->health) * 1000);
			if (li_blood > 200) {
				li_blood = 200;
			}
			ent->client->damage_blood = li_blood;

			/*
			// BOTS FIXME: Not working... :)

			// Show blood to other players
			AngleVectors (ent->client->ps.viewangles, forward, right, up);
			CalcMuzzlePoint ( ent, forward, right, up, muzzle );
			VectorMA (muzzle, 32, forward, end);

			trap_Trace (&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT);
			if ( tr.surfaceFlags & SURF_NOIMPACT ) {
				// do nothing
			}
			else {
				traceEnt = &g_entities[ tr.entityNum ];

				if ( traceEnt->takedamage && traceEnt->client ) {
					tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
					tent->s.otherEntityNum = traceEnt->s.number;
					tent->s.eventParm = DirToByte( tr.plane.normal );
					tent->s.weapon = ent->s.weapon;
				}
			}
			*/
		}
	}

	if (ent->client->killtimer > 0 && ent->client->killtimer < level.time) {
		ent->flags &= ~FL_GODMODE;
		ent->client->ps.stats[STAT_HEALTH] = ent->health = 0;
		player_die (ent, ent, ent, 100000, MOD_SUICIDE);
		ent->client->killtimer = 0;
	}

	bots_QuadRally();

	if (ent->bots_class != BOTS_BERZERKER) {
		if (ent->client->i_rageStopTime > level.time)
			ent->takedamage = qfalse;
		else
		{
			// rage has expired... kill the zerk
			if (ent->client->qb_rageMode)
			{
				ent->takedamage = qtrue;
				ent->client->qb_rageMode = qfalse;
				ent->flags &= ~FL_GODMODE;
			}
		}
	}

	return qtrue;
}


void bots_FragScoreboardMessage( gentity_t *ent ) {
	char		entry[1024];
	char		string[1400];
	int			stringlength;
	int			i, j;
	gclient_t	*cl;
	int			numSorted;
	int			scoreFlags;

	// send the latest information on all clients
	string[0] = 0;
	stringlength = 0;
	scoreFlags = 0;

	// don't send more than 32 scores (FIXME?)
	numSorted = level.numConnectedClients;
	if ( numSorted > 32 ) {
		numSorted = 32;
}

	for (i=0 ; i < numSorted ; i++) {
		int		ping;

		cl = &level.clients[level.sortedClients[i]];

		if ( cl->pers.connected == CON_CONNECTING ) {
			ping = -1;
		} else {
			ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
		}
		Com_sprintf (entry, sizeof(entry),
			" %i %i %i %i %i %i", level.sortedClients[i],
			cl->ps.persistant[PERS_FRAGS], ping, (level.time - cl->pers.enterTime)/60000,
			scoreFlags, g_entities[level.sortedClients[i]].s.powerups);
		j = strlen(entry);
		if (stringlength + j > 1024)
			break;
		strcpy (string + stringlength, entry);
		stringlength += j;
	}

	trap_SendServerCommand( ent-g_entities, va("scores %i %i %i%s", i,
		level.teamFrags[TEAM_RED], level.teamFrags[TEAM_BLUE],
		string ) );
}


qboolean bots_ConsoleClientCommand( gentity_t *ent, const char *cmd ) {

	// Standard check to make sure the client is valid
	if (!ent->client)
		return qfalse;

	if (Q_stricmp (cmd, "modversion") == 0) {
		BOTS_cp_console( ent->client->ps.clientNum, "Battle of the Sexes v1.2\nBuild 2001.01.31" );
		return qtrue;
	}
	else if (Q_stricmp (cmd, "frags") == 0) {
		bots_FragScoreboardMessage( ent );
		return qtrue;
	}
	else if (Q_stricmp (cmd, "coord") == 0) {
		bots_Coordinates( ent );
		return qtrue;
	}

	return qfalse;
}

void BOTS_radio(gentity_t *self, char *arg) {

	if (self->client) {
		int			team = self->client->sess.sessionTeam, msgNum;
		gentity_t	*tent;


		msgNum = atoi(arg);
		if ((msgNum<0) || (msgNum >= iMAX_RADIO_MSG)) {
			BOTS_console(self->client->ps.clientNum,"Invalid radio message number");
			return;
		}

		/*
		for (p = g_entities; p< &g_entities[level.num_entities]; p++) {
			if ((!p->client) || (p->client->sess.sessionTeam != team))
				continue;

			G_AddEvent(p,EV_RADIO,msgNum);
			p->s.otherEntityNum = self->s.number;
		}
		*/

		// Argh! otherEntityNum cannot be used on something else than a tempEntity.
		// I know i'm not doing the Right Thing[tm]...
		tent = G_TempEntity( self->r.currentOrigin, EV_RADIO );

		tent->s.otherEntityNum	= self->s.number;
		tent->s.eventParm		= msgNum;
		tent->s.powerups		= team;
		// there's 2 slots left for something usefull
		// tent->s.powerups
		// tent->s.otherEntityNum2;
		tent->r.svFlags = SVF_BROADCAST;	// send to everyone
	}
}

